//////////////////////////////////////////////////////////////////////////
/// Copyright (c) 2011, Sidorenko 'PC' Alexander                       ///
/// mailto: sidorenko /*dot*/ alexander (at) gmail [dot] com           ///
/// This file is a part of the Radiating Gas Dynamics project          ///
//////////////////////////////////////////////////////////////////////////

#ifndef ProblemN_INCLUDED
#define ProblemN_INCLUDED

#include "Godunov/GodunovSolverMT.h"
#include "ThreadCachedArray.h"
#include "DIB24.h"

const unsigned int STATES_BUFFER_SIZE = 10; // With current FPS of 120, it's 2 seconds of playback

template<unsigned int N>
struct TimeStep
{
	TimeStep() {}
	TimeStep(gdScalar time_) : time(time_) {}
	~TimeStep() {}

	typename GodunovSolverMT<N>::Grid grid;
	gdScalar time;
};

template<unsigned int N>
class ProblemN : public Problem
			   , public GodunovSolverMT<N>
			   , public ThreadCachedArray<TimeStep<N>, STATES_BUFFER_SIZE>
{
public:

	ProblemN(Type type, gdScalar gamma, gdScalar cfl, unsigned int numThreads, BOOL saveFull);
	virtual ~ProblemN();

	// Overloaded from Problem
	virtual bool openFile(const std::string& fileName);

	// Overloaded from Problem
	virtual void setListener(ProblemListener* pListener) { GodunovSolverMT<N>::setListener(pListener); }

	// Overloaded from Problem
	virtual void solve(gdScalar time, const std::string& fileName);
	virtual gdScalar getTime(unsigned int timeStep) const;
	virtual unsigned int getNumSteps() const;

	inline gdScalar getTotalTime() const { return m_modelTime; }

protected:

	void resizeDrawBuffer(const CSize& size)
	{
		if (m_buffer.getSize() != size)
		{
			m_buffer.updateBitmapHeader(size);
			m_buffer.allocMemory();
		}
		if (!m_buffer.isMemAllocated())
			m_buffer.allocMemory();
	}


	typedef GodunovSolverMT<N>::Grid Grid;
	
//	typedef std::vector<TimeStep> Solution;
//	typedef TimeStep Solution[STATES_BUFFER_SIZE];

//	Solution m_solution;

	TimeStep<N> m_gridBuffer[STATES_BUFFER_SIZE];

	Cell m_minValues, m_maxValues;
	unsigned int m_dimensions[N];
	unsigned int m_numTimesteps;
	gdScalar m_modelTime;

	CDIB24 m_buffer;

private:

	// Overloaded from ThreadCachedArray
	virtual void readToCache(TimeStep<N>* data, unsigned int hugeFileID, HANDLE file);

};

template<unsigned int N>
ProblemN<N>::ProblemN(Type type, gdScalar gamma, gdScalar cfl, unsigned int numThreads, BOOL saveFull)
: Problem(type)
, GodunovSolverMT<N>(gamma, cfl, numThreads, saveFull)
, m_modelTime(0.0)
, ThreadCachedArray<TimeStep<N>, STATES_BUFFER_SIZE>(m_gridBuffer)
{
}

template<unsigned int N>
ProblemN<N>::~ProblemN()
{
	stopCaching();
}

template<unsigned int N>
bool ProblemN<N>::openFile(const std::string& fileName)
{
	FILE *file = fopen(fileName.c_str(), "rb");
	if (NULL == file)
	{
		return false;
	}

	FileHeader header;

	fread(&header, sizeof(header), 1, file);

	_ASSERT(header.type == m_type);

	m_minValues = header.minVals;
	m_maxValues = header.maxVals;
	m_modelTime = header.modelTime;
	m_numTimesteps = header.numTimesteps;
	m_gamma = header.gamma;
	m_cfl = header.cfl;
	m_minCellSize = header.minCellSize;
	m_numThreads = header.numThreads;
	m_solvingTime = header.solvingTime;
	memcpy(m_dimensions, header.dimensions, sizeof(header.dimensions[0]) * N);

	fclose(file);

	unsigned int sz = 1;
	for (unsigned int i = 0; i < N; ++i)
		sz *= header.dimensions[i];

	// Let's resize buffer
	for (unsigned int i = 0; i < STATES_BUFFER_SIZE; ++i)
		m_gridBuffer[i].grid.resize(sz);

	startCaching(fileName);

	return true;
}

template<unsigned int N>
void ProblemN<N>::solve(gdScalar time, const std::string& fileName)
{
	FILE *file = fopen(fileName.c_str(), "wb");
	if (NULL == file)
	{
		m_pListener->onStepCompleated(time, time, 0);
		return;
	}

	// Let's write file header in the beginning
	FileHeader header; // It will be overwritten after simulation,
					   // so we don't care about it now. Just a placeholder
	header.type = m_type;
	memcpy(header.dimensions, m_dimensions, sizeof(m_dimensions[0]) * N);

	fwrite(&header, sizeof(header), 1, file);

	GodunovSolverMT<N>::SolveResult res = GodunovSolverMT<N>::solve(time, file);

	m_minValues = res.minValues;
	m_maxValues = res.maxValues;
	m_numTimesteps = res.numTimesteps;
	m_modelTime = res.modelTime;

	header.maxVals = res.maxValues;
	header.minVals = res.minValues;
	header.numTimesteps = res.numTimesteps;
	header.modelTime = res.modelTime;
	header.solvingTime = res.solvingTime;
	header.numThreads = m_numThreads;
	header.gamma = m_gamma;
	header.minCellSize = m_minCellSize;
	header.cfl = m_cfl;
	header.precision = m_riemann.getTolerance();

	fseek(file, 0, SEEK_SET);
	fwrite(&header, sizeof(header), 1, file);

	fclose(file);
}

template<unsigned int N>
gdScalar ProblemN<N>::getTime(unsigned int timeStep) const
{
	//_ASSERT(timeStep < m_solution.size());
	//return m_solution[timeStep].time;
	// TODO:
	return 0;
}

template<unsigned int N>
unsigned int ProblemN<N>::getNumSteps() const
{
	return m_numTimesteps;
//	return (unsigned int)m_solution.size();
}

template<unsigned int N>
void ProblemN<N>::readToCache(TimeStep<N>* data, unsigned int hugeFileID, HANDLE file)
{
	_ASSERT(NULL != file);
	LARGE_INTEGER offset;
	DWORD dwBytesWritten = 0;

	offset.QuadPart = LONGLONG(sizeof(FileHeader));
	offset.QuadPart += LONGLONG(sizeof(data->time)) * LONGLONG(hugeFileID);
	offset.QuadPart += LONGLONG(sizeof(data->grid[0]) * data->grid.size()) * LONGLONG(hugeFileID);
//	unsigned int offset = sizeof(FileHeader) + (sizeof(data->time) + sizeof(data->grid[0]) * m_dimensions[0]) * hugeFileID;
//	fseek(file, offset, SEEK_SET);
	SetFilePointerEx(file, offset, NULL, FILE_BEGIN);

//	fread(&data->time, sizeof(data->time), 1, file);
	ReadFile(file, &data->time, sizeof(data->time), &dwBytesWritten, NULL);

//	fread(&data->grid[0], sizeof(GodunovSolverMT<N>::Cell), m_dimensions[0], file);
	ReadFile(file, &data->grid[0], sizeof(GodunovSolverMT<N>::Cell) * data->grid.size(), &dwBytesWritten, NULL);
}

#endif // ProblemN_INCLUDED